package asruby.lang
{
import asruby.Ruby;
import asruby.runtime.Block;
import asruby.runtime.CallType;
import asruby.runtime.IRubyObject;
import asruby.runtime.ObjectAllocator;
import asruby.runtime.ThreadContext;

public class RubyClass extends RubyModule {
	
    public static function  createClassClass(runtime: Ruby, classClass: RubyClass): void {
        classClass.index = ClassIndex.CLASS;
        classClass.kindOf = function  isKindOf(obj: IRubyObject, type: RubyModule): Boolean {
                return obj instanceof RubyClass;
        }
        
        classClass.undefineMethod("module_function");
        classClass.undefineMethod("append_features");
        classClass.undefineMethod("extend_object");
        
        classClass.defineAnnotatedMethods(RubyClass);
        
        // This is a non-standard method; have we decided to start extending Ruby?
        //classClass.defineFastMethod("subclasses", callbackFactory.getFastOptMethod("subclasses"));
        
        // FIXME: for some reason this dispatcher causes a VerifyError...
        //classClass.dispatcher = callbackFactory.createDispatcher(classClass);
    }
    
     public static function CLASS_ALLOCATOR(runtime: Ruby, klass: RubyClass): IRubyObject {
        var clazz: RubyClass = new RubyClass(runtime);
        clazz.allocator = ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR; // Class.allocate object is not allocatable before it is initialized
        return clazz;
    };

    public function  getAllocator(): Function {
        return allocator;
    }

    public function  setAllocator(allocator: Function): void {
        this.allocator = allocator;
    }

    //@JRubyMethod(name = "allocate")
    public function  allocate(): IRubyObject {
        if (superClass == null) throw runtime.newTypeError("can't instantiate uninitialized class");
        var obj: IRubyObject = allocator.allocate(runtime, this);
        if (obj.getMetaClass().getRealClass() != getRealClass()) throw runtime.newTypeError("wrong instance allocation");
        return obj;
    }    

    public override function  getNativeTypeIndex(): int {
        return ClassIndex.CLASS;
    }
    
    public override function  isModule(): Boolean {
        return false;
    }

    public override function  isClass(): Boolean {
        return true;
    }

    public override function  isSingleton(): Boolean {
        return false;
    }

    /** boot_defclass
     * Create an initial Object meta class before Module and Kernel dependencies have
     * squirreled themselves together.
     * 
     * @param runtime we need it
     * @return a half-baked meta class for object
     */
    public static function createBootstrapClass(runtime: Ruby, name: String, superClass: RubyClass, allocator: Function): RubyClass {
        var obj:RubyClass;

        if (superClass == null ) {  // boot the Object class 
            obj = new RubyClass(runtime);
            obj.marshal = DEFAULT_OBJECT_MARSHAL;
        } else {                    // boot the Module and Class classes
            obj = new RubyClass(runtime, superClass);
        }
        obj.setAllocator(allocator);
        obj.setBaseName(name);
        return obj;
    }

    private var runtime: Ruby;
    private var allocator:Function; 
    private var marshalObject: ObjectMarshal;
    private var subclassesArray: Array;

    /** separate path for MetaClass and IncludedModuleWrapper construction
     *  (rb_class_boot version for MetaClasses)
     *  no marshal, allocator initialization and addSubclass(this) here!
     */
    public function RubyClass(runtime:Ruby,  superClass:RubyClass,  objectSpace:Boolean) {
        super(runtime, runtime.getClassClass(), objectSpace);
        this.runtime = runtime;
        this.superClass = superClass; // this is the only case it might be null here (in MetaClass construction)
    }
    
    /** used by CLASS_ALLOCATOR (any Class' class will be a Class!)
     *  also used to bootstrap Object class
     */
 /*    protected RubyClass( runtime:Ruby) {
        super(runtime, runtime.getClassClass());
        var this.runtime:  = runtime;
        var index:  = ClassIndex.CLASS;
    } */
    
    /** rb_class_boot (for plain Classes)
     *  also used to bootstrap Module and Class classes 
     */
/*     protected RubyClass(Ruby runtime, RubyClass superClazz) {
        this(runtime);
        var superClass:  = superClazz;
        var marshal:  = superClazz.marshal; // use parent's marshal
        superClazz.addSubclass(this);
        
        infectBy(superClass);        
    } */

    /** rb_class_new
     * 
     */
    public static function  newClass(runtime: Ruby, superClass: RubyClass): RubyClass {
        if (superClass == runtime.getClassClass()) throw runtime.newTypeError("can't make subclass of Class");
        if (superClass.isSingleton()) throw runtime.newTypeError("can't make subclass of virtual class");
        return new RubyClass(runtime, superClass);        
    }

    /** rb_class_new/rb_define_class_id/rb_name_class/rb_set_class_path
     * 
     */
    public static function  newClass2(runtime: Ruby, superClass: RubyClass, name: String, allocator: Function, parent: RubyModule, setParent: Boolean): RubyClass {
        var clazz: RubyClass = newClass(runtime, superClass);
        clazz.setBaseName(name);
        clazz.setAllocator(allocator);
        clazz.makeMetaClass(superClass.getMetaClass());
        if (setParent) clazz.setParent(parent);
        parent.setConstant(name, clazz);
        clazz.inherit(superClass);
        return clazz;
    }

    /** rb_make_metaclass
     *
     */
    public override function  makeMetaClass(superClass: RubyClass): RubyClass {
        if (isSingleton()) { // could be pulled down to RubyClass in future
            var klass: MetaClass = new MetaClass(getRuntime(), superClass); // rb_class_boot
            setMetaClass(klass);

            klass.setAttached(this);
            klass.setMetaClass(klass);
            klass.setSuperClass(getSuperClass().getRealClass().getMetaClass());
            
            return klass;
        } else {
            return super.makeMetaClass(superClass);
        }
    }
    
    public function invoke(context: ThreadContext, self: IRubyObject, methodIndex: int, name: String,
    		args:Array, callType:CallType, block: Block): IRubyObject {
        if (context.getRuntime().hasEventHooks()) 
        	return invoke(context, self, name, args, callType, block);     
        return dispatcher.callMethod(context, self, this, methodIndex, name, args, callType, block);
    }
    
    public function invoke2(context:ThreadContext, self:IRubyObject, name:String,
            args:Array, callType:CallType,  block:Block): IRubyObject {
        //assert args != null;
        var method: DynamicMethod = null;
        method = searchMethod(name);
        

        if (method.isUndefined() || (!name.equals("method_missing") && !method.isCallableFrom(context.getFrameSelf(), callType))) {
            return RuntimeHelpers.callMethodMissing(context, self, method, name, args, context.getFrameSelf(), callType, block);
        }

        return method.call(context, self, this, name, args, block);
    }
    
    public function  invoke3(context:ThreadContext, self:IRubyObject, name:String,
            args:Array, callType:CallType,  block:Block): IRubyObject {
        var method: DynamicMethod = null;
        method = searchMethod(name);
        

        if (method.isUndefined() || (!name.equals("method_missing") && !method.isCallableFrom(context.getFrameSelf(), callType))) {
            return RuntimeHelpers.callMethodMissing(context, self, method, name, [arg], context.getFrameSelf(), callType, block);
        }

        return method.call(context, self, this, name, arg, block);
    }
    
    public function invokeInherited(context:ThreadContext, self:IRubyObject, subclass:IRubyObject):IRubyObject {
        var name:String = "inherited";
        var method: DynamicMethod = getMetaClass().searchMethod(name);
        var args: Array = [subclass];

        if (method.isUndefined()) {
            return RuntimeHelpers.callMethodMissing(context, self, method, name, args, 
            		context.getFrameSelf(), CallType.FUNCTIONAL, Block.NULL_BLOCK);
        }

        return method.call(context, self, getMetaClass(), name, args, Block.NULL_BLOCK);
    }

    /** rb_class_new_instance
    *
    */
    //@JRubyMethod(name = "new", rest = var frame: true = true)
    public function  newInstance(context: ThreadContext, args: Array, block: Block): IRubyObject {
        var obj: IRubyObject = allocate();
        obj.callMethod(context, "initialize", args, block);
        return obj;
    }

    /** rb_class_initialize
     * 
     */
    //@JRubyMethod(name = "initialize", optional = 1, frame = var visibility: true = Visibility.PRIVATE)
    public override function  initialize(args: Array, block: Block): IRubyObject {
        if (superClass != null) {
            System.out.println(classId);
            throw getRuntime().newTypeError("already initialized class");
        }
 
        var superObject:IRubyObject;
        if (args.length == 0) {
            superObject = getRuntime().getObject();
        } else {
            superObject = args[0];
            checkInheritable(superObject);
        }
 
        var superClazz:RubyClass = superObject;

        superClass = superClazz;
        allocator = superClazz.allocator;
        makeMetaClass(superClazz.getMetaClass());
        
        this.marshal = superClazz.marshal;
       
        superClazz.addSubclass(this);
       
        super.initialize(block);
       
        inherit(superClazz);

        return this;        
    }    

    /** rb_class_init_copy
     * 
     */
    //@JRubyMethod(name = var required: "initialize_copy" = 1)
    public override function  initialize_copy(original: IRubyObject): IRubyObject {
        if (superClass != null) throw runtime.newTypeError("already initialized class");
        if (original instanceof MetaClass) throw getRuntime().newTypeError("can't copy singleton class");        
        
        super.initialize_copy(original);
        allocator = original.allocator; 
        return this;        
    }
    
    // TODO: Someday, enable.
    // //@JRubyMethod(name = var optional: "subclasses" = 1)
    public function  subclasses(args: Array): IRubyObject {
        var recursive: Boolean = false;
        if (args.length == 1) {
            if (args[0] instanceof RubyBoolean) {
                recursive = args[0].isTrue();
            } else {
                getRuntime().newTypeError(args[0], getRuntime().fastGetClass("Boolean"));
            }
        }
        
        return RubyArray.newArray(getRuntime(), subclasses2(recursive)).freeze();
    }
    
    public function  subclasses2(includeDescendants: Boolean): Array {
        if (subclasses != null) {
            var mine: Array = [subclasses];
            if (includeDescendants) {
                for (var i:RubyClass in  subclasses) {
                    mine.addAll(i.subclasses(includeDescendants));
                }
            }

            return mine;
        } else {
            return Collections.EMPTY_LIST;
        }
    }
    
    public function  addSubclass(subclass: RubyClass): void {
        if (subclasses == null) subclasses = new Dictionary();
        subclasses.add(subclass);
    }
    
    public function  getClassRuntime(): Ruby {
            return runtime;
    }

    public function  getRealClass(): RubyClass {
        return this;
    }    

    //@JRubyMethod(name = var required: "inherited" = 1)
    public function  inherited(arg: IRubyObject): IRubyObject {
        return getRuntime().getNil();
    }

    /** rb_class_inherited (reversed semantics!)
     * 
     */
    public function  inherit(superClazz: RubyClass): void {
        if (superClazz == null) superClazz = getRuntime().getObject();
        superClazz.invokeInherited(
                getRuntime().getCurrentContext(), superClazz,
                this);
    }

    /** Return the real super class of this class.
     * 
     * rb_class_superclass
     *
     */
    //@JRubyMethod(name = "superclass")
    public function  superclass(): IRubyObject {
        var superClazz: RubyClass = superClass;

        if (superClazz == null) throw runtime.newTypeError("uninitialized class");
        
        if(isSingleton()) superClazz = metaClass;
        while (superClazz != null && superClazz.isIncluded()) superClazz = superClazz.superClass;

        return superClazz != null ? superClazz : getRuntime().getNil();
    }

    /** rb_check_inheritable
     * 
     */
    public static function  checkInheritable(superClass: IRubyObject): void {
        if (!(superClass instanceof RubyClass)) {
            throw superClass.getRuntime().newTypeError("superclass must be a Class (" + superClass.getMetaClass() + " given)"); 
        }
        if (superClass.isSingleton()) {
            throw superClass.getRuntime().newTypeError("can't make subclass of virtual class");
        }        
    }

/*     public final function  getMarshal(): ObjectMarshal {
        return this.marshal;
    }
    
    public final function  setMarshal(marshal: ObjectMarshal): void {
        this.marshal = marshal;
    }
    
    public final function  marshal(obj: Object, marshalStream: MarshalStream): void {
        getMarshal().marshalTo(getRuntime(), obj, this, marshalStream);
    }
    
    public final function  unmarshal(unmarshalStream: UnmarshalStream): Object {
        return getMarshal().unmarshalFrom(getRuntime(), this, unmarshalStream);
    }
    
    public static function  marshalTo(clazz: RubyClass, output: MarshalStream): void {
        output.registerLinkTarget(clazz);
        output.writeString(MarshalStream.getPathFromClass(clazz));
    }

    public static function  unmarshalFrom(input: UnmarshalStream): RubyClass {
        var name: String = RubyString.byteArrayToString(input.unmarshalString());
        var result: RubyClass = UnmarshalStream.getClassFromPath(input.getRuntime(), name);
        input.registerLinkTarget(result);
        return result;
    } 

    protected static var DEFAULT_OBJECT_MARSHAL: ObjectMarshal = new ObjectMarshal(
		function marshalTo( runtime:Ruby,  obj:Object,  type:RubyClass,
                               marshalStream:MarshalStream) {
            var object:IRubyObject = IRubyObject(obj);
            
            marshalStream.registerLinkTarget(object);
            marshalStream.dumpVariables(object.getVariableArray());
        }
		,
       	function  unmarshalFrom(runtime:Ruby,  obj:Object,  type:RubyClass,
                               marshalStream:MarshalStream):Object {
            var result: IRubyObject = type.allocate();
            
            unmarshalStream.registerLinkTarget(result);

            unmarshalStream.defaultVariablesUnmarshal(result);

            return result;
        }
    ); */  
    public function setAttached(runtime:RubyClass):void{
    	
    }  
}

}